package cn.itsource.ymcc.service.impl;

import cn.itsource.ymcc.domain.CourseType;
import cn.itsource.ymcc.mapper.CourseTypeMapper;
import cn.itsource.ymcc.service.ICourseTypeService;
import cn.itsource.ymcc.util.AssertUtil;
import cn.itsource.ymcc.vo.CourseTypeCrumbsVo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 课程目录 服务实现类
 * </p>
 *
 * @author whale.chen
 * @since 2023-02-27
 */
@Service
public class CourseTypeServiceImpl extends ServiceImpl<CourseTypeMapper, CourseType> implements ICourseTypeService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    private static final String COURSE_TYPE_TREE_DATA = "course_type_tree_data";

    /**
     * @CacheEvict注解贴在增删改方法上面，用来删除缓存
     *  底层执行了一个命令：redisTemplate.delete("course_type_tree_data::ALL")
     * @param entity
     * @return
     */
    @Transactional
    @CacheEvict(cacheNames = COURSE_TYPE_TREE_DATA, key = "'ALL'")
    @Override
    public boolean save(CourseType entity) {
        super.save(entity);

        //维护path字段：等于 它父亲的path.它自己的id
        CourseType parent = baseMapper.selectById(entity.getPid());
        entity.setPath(parent.getPath() + "." + entity.getId());
        super.updateById(entity);
        return true;
    }

    @Transactional
    @CacheEvict(cacheNames = COURSE_TYPE_TREE_DATA, key = "'ALL'")
    @Override
    public boolean removeById(Serializable id) {
        List<CourseType> children = baseMapper.selectList(new QueryWrapper<CourseType>().eq("pid", id));
        AssertUtil.isTrue(children == null || children.size() == 0, "该课程类别下面还有子类别，不能删除！");
        return super.removeById(id);
    }

    @Transactional
    @CacheEvict(cacheNames = COURSE_TYPE_TREE_DATA, key = "'ALL'") //
    @Override
    public boolean updateById(CourseType entity) {
        return false;
    }

    /**
     * 查询课程类别树
     * 要使用SpringCache，首先需要在启动类或者配置上面添加@EnableCaching注解，开启
     * 然后在查询方法上面添加@Cacheable注解：这个注解内部都封装了我们刚才所写的缓存流程逻辑。
     *  cacheNames：指定一个缓存名称
     *  key：指定一个缓存key
     *  SpringCache会将cacheNames和key之间拼接两个冒号    course_type_tree_data::ALL
     *  注意：key属性是一个表达式，如果要写字符串，必须添加单引号！！！
     *
     *  底层原理：
     *      采用了SpringAOP技术（动态代理），切到当前@Cacheable注解所添加的方法上面；
     *      在子类中重写了treeData方法，子类重写的方法中就是按照我们之前自己写的逻辑实现的。
     *
     * @return 返回最顶层级别的类别集合，所有的非顶级类别都装在他父亲的儿子集合中
     */
    @Cacheable(cacheNames = COURSE_TYPE_TREE_DATA, key = "'ALL'")
    @Override
    public List<CourseType> treeData() {
//        //到数据库查询之前，先查询Redis中是否有缓存数据
//        if(redisTemplate.hasKey(COURSE_TYPE_TREE_DATA)){
//            return (List<CourseType>) redisTemplate.opsForValue().get(COURSE_TYPE_TREE_DATA);
//        }
//        //缓存中没有数据，就到数据库中查询，再缓存一份
//        List<CourseType> courseTypes =  forMap();
//        redisTemplate.opsForValue().set(COURSE_TYPE_TREE_DATA, courseTypes);
//        return courseTypes;
        System.out.println("进入service层方法去查询数据库........................");
        return forMap();
    }

    /**
     * 加载面包屑数据
     *  1.先通过传入的courseTypeId查询一行数据
     *  2.获取它的path字段
     *  3.path字段值以.拆分字符串，得到数组
     *  4.循环
     *      查询本次循环的课程类别id对应的一行数据
     *      查询本次循环的课程类别id对应的兄弟集合
     *      封装CourseTypeCrumbsVo对象放入list集合中
     * @param courseTypeId
     * @return
     */
    @Override
    public List<CourseTypeCrumbsVo> loadCrumbs(Long courseTypeId) {
        List<CourseTypeCrumbsVo> voList = new ArrayList<>();
        //1.先通过传入的courseTypeId查询一行数据
        CourseType courseType = baseMapper.selectById(courseTypeId);
        //2.获取它的path字段     1037.1039.1048
        String path = courseType.getPath();
        //3.path字段值以.拆分字符串，得到数组   [1037,1039,1048]
        String[] split = path.split("\\.");
        //4.循环
        for (String s : split) {
            //查询本次循环的课程类别id对应的一行数据 1037
            CourseType current = baseMapper.selectById(s);
            //查询本次循环的课程类别id对应的兄弟集合：它的兄弟的pid与它的pid相等
            List<CourseType> brothers = baseMapper.selectList(new QueryWrapper<CourseType>()
                    .eq("pid", current.getPid()));
            //封装CourseTypeCrumbsVo对象放入list集合中
            voList.add(new CourseTypeCrumbsVo(current,brothers));
        }
        return voList;
    }

    /**
     * 1.查询课程类别树-递归写法：传入pid，查询它的儿子，直到它的儿子集合为空为止
     *  缺点：
     *      数据库查询次数太多了，数据库压力太大；
     *      递归代码阅读性差；
     * @param pid
     * @return
     */
    public List<CourseType> recursion(Long pid){
        //根据pid查询数据，得到的是它的儿子
        List<CourseType> courseTypes = baseMapper.selectList(new QueryWrapper<CourseType>()
                .eq("pid", pid));
        if(courseTypes != null && courseTypes.size() > 0){
            for (CourseType courseType : courseTypes) {
                List<CourseType> children = recursion(courseType.getId());
                courseType.setChildren(children);
            }
        }
        return courseTypes;
    }

    /**
     * 2.查询课程类别树-for循环嵌套
     *  优点：一次性将所有课程类别查询出来，降低了数据库压力
     *  缺点：循环次数太多，代码效率低：循环次数 = 数据量的平方
     * @return
     */
    public List<CourseType> forNestification(){
        //顶级课程类别集合
        List<CourseType> firstLevels = new ArrayList<>();
        //一次性将所有课程类别查询出来，降低了数据库压力
        List<CourseType> courseTypes = baseMapper.selectList(null);

        //组装树形结构
        CourseType parent = null;
        for (CourseType courseType : courseTypes) {
            if(courseType.getPid() == 0){
                //判断当前循环的课程类别是不是一级？如果是一级，就直接放入到顶级课程类别集合中
                firstLevels.add(courseType);
            }else{
                //否则就不是一级，就找到它爹，把它设置成它爹的儿子
                for (CourseType ct : courseTypes) {
                    if(courseType.getPid().longValue() == ct.getId().longValue()){
                        parent = ct;
                        break;
                    }
                }
                parent.getChildren().add(courseType);
            }
        }
        return firstLevels;
    }

    /**
     * 3.查询课程类别树-for循环+HashMap
     *  效率高：循环次数少 = 数据量 * 2
     * @return
     */
    public List<CourseType> forMap(){
        //顶级课程类别集合
        List<CourseType> firstLevels = new ArrayList<>();
        //一次性将所有课程类别查询出来，降低了数据库压力
        List<CourseType> courseTypes = baseMapper.selectList(null);

        //先将所有课程类别封装到一个HashMap中：以课程类别id为key，以课程类别对象为值
//        Map<Long, CourseType> allCourseTypeMap = new HashMap<>();
//        courseTypes.forEach(courseType -> allCourseTypeMap.put(courseType.getId(), courseType));

        Map<Long, CourseType> allCourseTypeMap = courseTypes.stream()
                .collect(Collectors.toMap(courseType -> courseType.getId(), courseType -> courseType));

        //组装树形结构
        CourseType parent = null;
        for (CourseType courseType : courseTypes) {
            if(courseType.getPid() == 0){
                //判断当前循环的课程类别是不是一级？如果是一级，就直接放入到顶级课程类别集合中
                firstLevels.add(courseType);
            }else{
                //否则就不是一级，就找到它爹，把它设置成它爹的儿子
                parent = allCourseTypeMap.get(courseType.getPid());
                parent.getChildren().add(courseType);
            }
        }
        return firstLevels;
    }
}
